#include "preHeader.h"
#include "GameServerSession.h"
#include "GameServerSessionHandler.h"
#include "GateServerListener.h"
#include "MapServer.h"
#include "Cross/CrossServer.h"
#include "network/ConnectionManager.h"
#include "Map/InstanceMgr.h"
#include "Object/PlayerMgr.h"

GameServerSession::GameServerSession(const std::string& host,
	const std::string& port, uint32 service, size_t index)
: RPCSession("GameServerSession", GS_RPC_INVOKE_RESP)
, m_host(host)
, m_port(port)
, m_service(service)
, m_index(index)
, m_serverId(0)
, m_serviceId(0)
, m_sn(0)
{
}

GameServerSession::~GameServerSession()
{
}

void GameServerSession::CheckConnection()
{
	const auto& connPrePtr = GetConnection();
	if (connPrePtr && connPrePtr->IsActive()) {
		return;
	}
	if (!IsStatus(Idle)) {
		return;
	}

	auto connPtr = sConnectionManager.NewConnection(*this);
	connPtr->AsyncConnect(m_host, m_port);

	sSessionManager.AddSession(this);
}

int GameServerSession::HandlePacket(INetPacket *pck)
{
	return sGameServerSessionHandler.HandlePacket(this, *pck);
}

void GameServerSession::OnConnected()
{
	NetPacket req(MS_REGISTER);
	req << m_serviceId << service() << (u32)index()
		<< sGateServerListener.addr() << sGateServerListener.port();
	PushSendPacket(req);
	RPCSession::OnConnected();
}

void GameServerSession::OnShutdownSession()
{
	WLOG("Close GameServerSession [GS:%u MS:%u].",
		m_serverId, m_serviceId);
	sMapServer.OfflinePlayer(index());
	RPCSession::OnShutdownSession();
}

void GameServerSession::DeleteObject()
{
	ClearRecvPacket();
	ClearShutdownFlag();
	SetStatus(Idle);
}

void GameServerSession::SendPacket2SS(const INetPacket& pck)
{
	NetPacket toGS(MS_TO_SOCIAL_SERVER_PACKET);
	PushSendPacket(toGS, pck);
}

void GameServerSession::SendPacket2SS4Player(
	const std::string_view& info, const INetPacket& pck)
{
	NetPacket toGS(MS_TO_SOCIAL_SERVER_PACKET);
	NetPacket wrapper(pck.GetOpcode());
	const INetPacket *pcks[] = {&toGS, &wrapper};
	const std::string_view datas[] = {info, pck.CastReadableStringView()};
	PushSendPacket(pcks, ARRAY_SIZE(pcks), datas, ARRAY_SIZE(datas));
}

void GameServerSession::RPCInvoke2SS(const INetPacket &pck,
	const std::function<void(INetStream&, int32, bool)> &cb,
	AsyncTaskOwner *owner, time_t timeout)
{
	NetPacket toGS(MS_TO_SOCIAL_SERVER_PACKET);
	RPCTransInvoke(toGS, pck, cb, owner, timeout);
}

void GameServerSession::RPCReply2SS(
	const INetPacket &pck, uint64 sn, int32 err, bool eof)
{
	NetPacket toGS(MS_TO_SOCIAL_SERVER_PACKET);
	RPCTransReply(toGS, pck, sn, err, eof);
}

void GameServerSession::OnRecvPacket(INetPacket *pck)
{
	switch (pck->GetOpcode()) {
	case GS_TRANS_INSTANCE_PACKET:
		TransInstancePacket(pck);
		break;
	case GS_TRANS_PLAYER_PACKET:
		TransPlayerPacket(pck);
		break;
	case GS_TRANS_CROSS_SERVER_PACKET:
		TransCrossServerPacket(pck);
		break;
	default:
		RPCSession::OnRecvPacket(pck);
		break;
	}
}

void GameServerSession::TransInstancePacket(INetPacket *pck) const
{
	InstGUID instGuid;
	*pck >> instGuid;
	MapInstance* pMapInstance = sInstanceMgr.GetMapInstance(instGuid);
	if (pMapInstance != NULL) {
		pMapInstance->PushRecvPacket(
			InstanceService::NewInstancePacket(m_index, &pck->UnpackPacket()));
	} else {
		WLOG("Trans instance packet failed, can't find instance [%hu,%hu,%u].",
			instGuid.TID, instGuid.MAPID, instGuid.UID);
		delete pck;
	}
}

void GameServerSession::TransPlayerPacket(INetPacket *pck) const
{
	ObjGUID playerGuid;
	*pck >> playerGuid;
	playerGuid.SID = (uint16)m_index;
	InstGUID instGuid = sPlayerMgr.GetPlayerInstance(playerGuid);
	if (instGuid != InstGUID_NULL) {
		MapInstance* pMapInstance = sInstanceMgr.GetMapInstance(instGuid);
		if (pMapInstance != NULL) {
			pMapInstance->PushRecvPacket(
				InstanceService::NewPlayerPacket(playerGuid, &pck->UnpackPacket()));
		} else {
			WLOG("Trans player packet failed, can't find instance [%hu,%hu,%u].",
				instGuid.TID, instGuid.MAPID, instGuid.UID);
			delete pck;
		}
	} else {
		WLOG("Trans player packet failed, can't find player (%hu,%u).",
			playerGuid.SID, playerGuid.UID);
		delete pck;
	}
}

void GameServerSession::TransCrossServerPacket(INetPacket* pck) const
{
	sCrossServer.GetService()->PushRecvPacket(&pck->UnpackPacket());
}

int GameServerSessionHandler::HandleRegisterResp(GameServerSession *pSession, INetPacket &pck)
{
	pck >> pSession->m_serviceId >> pSession->m_serverId >> pSession->m_sn;
	if (pSession->m_serviceId == 0) {
		WLOG("Register Fail, Now EXIT...");
		return SessionHandleKill;
	}
	pSession->OnRPCSessionReady();
	NLOG("Register to GameServer Success As [GS:%u MS:%u], Starting maps...",
		pSession->m_serverId, pSession->m_serviceId);
	return SessionHandleSuccess;
}

int GameServerSessionHandler::HandlePushServerId(GameServerSession *pSession, INetPacket &pck)
{
	pck >> pSession->m_serverId;
	NLOG("Push game server id: %u.", pSession->m_serverId);
	return SessionHandleSuccess;
}

int GameServerSessionHandler::HandleStartWorldMap(GameServerSession *pSession, INetPacket &pck)
{
	uint32 mapId;
	pck >> mapId;
	if (mapId != u32(-1)) {
		auto instGuid = MakeInstGuid((u32)MapInfo::Type::WorldMap, mapId, 0);
		if (sInstanceMgr.GetMapInstance(instGuid) == NULL) {
			auto pMapInstance =
				sInstanceMgr.CreateInstance(instGuid.TID, instGuid.MAPID, instGuid.UID);
			if (pMapInstance != NULL) {
				pMapInstance->DoneOneInitTask();
				NLOG("Map started: map[%u]", mapId);
			} else {
				WLOG("Map start failed: map[%u]", mapId);
			}
		}
	} else {
		NLOG("Map started: All done");
	}
	return SessionHandleSuccess;
}

int GameServerSessionHandler::HandleStartInstance(GameServerSession *pSession, INetPacket &pck)
{
	InstGUID fakeInstGuid;
	ObjGUID instOwner;
	uint32 opCodeResp, flags;
	pck >> fakeInstGuid >> instOwner >> opCodeResp >> flags;
	if (instOwner != ObjGUID_NULL) {
		instOwner = GetGuidFromValue((u16)pSession->index(), instOwner.objGUID);
	}
	sInstanceMgr.StartInstance(
		fakeInstGuid, instOwner, flags, pck.CastReadableStringView(),
		[=](bool isSucc, InstGUID instGuid) {
		if (opCodeResp != OPCODE_NONE) {
			NetPacket resp(opCodeResp);
			resp << isSucc << flags << instGuid << fakeInstGuid;
			pSession->PushSendPacket(resp);
		}
	});
	return SessionHandleSuccess;
}

int GameServerSessionHandler::HandleStopInstance(GameServerSession *pSession, INetPacket &pck)
{
	InstGUID instGuid;
	pck >> instGuid;
	sInstanceMgr.StopInstance(instGuid);
	return SessionHandleSuccess;
}

int GameServerSessionHandler::HandleCharacterTeleportBeginEnterInstance(GameServerSession *pSession, INetPacket &pck)
{
	std::shared_ptr<CharTeleportInfo> tpInfoPtr = std::make_shared<CharTeleportInfo>();
	CharTeleportInfo& tpInfo = *tpInfoPtr;
	LoadFromINetStream(tpInfo, pck);

	uint16 gsIdx = (uint16)pSession->index();
	ObjGUID playerGuid = GetGuidFromValue(gsIdx, tpInfo.playerGuid);
	ObjGUID instOwner = tpInfo.ownerGuid != 0 ?
		GetGuidFromValue(gsIdx, tpInfo.ownerGuid) : ObjGUID_NULL;
	InstGUID instGuid = GetInstGuidFromValue(tpInfo.instGuid);

	MapInstance* pMapInstance = sInstanceMgr.CreateAndGetInstance(
		instGuid.TID, instGuid.MAPID, instGuid.UID, instOwner);
	if (pMapInstance == NULL) {
		NetPacket resp(MS_CHARACTER_TELEPORT_BEGIN_ENTER_INSTANCE_RESULT);
		resp << InstanceNotFound << tpInfo.playerGuid;
		pSession->PushSendPacket(resp);
		return SessionHandleSuccess;
	}

	pMapInstance->AddEventSafe(std::bind(
		&MapInstance::EventAddPendingEnterPlayer, pMapInstance, gsIdx, tpInfoPtr));

	return SessionHandleSuccess;
}

int GameServerSessionHandler::HandleSyncPlayerLevelMax(GameServerSession* pSession, INetPacket& pck)
{
	uint32 playerLevelMax;
	pck >> playerLevelMax;
	pSession->getVData().SetPlayerLevelMax(playerLevelMax);
	return SessionHandleSuccess;
}

int GameServerSessionHandler::HandleSyncOperatingCares(GameServerSession *pSession, INetPacket &pck)
{
	std::string operatingCares;
	pck >> operatingCares;
	pSession->getVData().SetOperatingCares(operatingCares);
	return SessionHandleSuccess;
}
